/*
 *  keyboard.c
 */

#include "../include/keyboard.h"
#include "../include/key_layouts.h"
#include "../include/ctype.h"
#include "../include/kernel.h"
#include "../include/video.h"
#include "../include/defs.h"
#include "../include/arqDefs.h"

#define	OK              1
#define LEDS(capslock, numlock, scrollock)  (capslock) * 4 + (numlock) * 2 + (scrollock);
/*
 *  Estructuras
 */

/*
 *  Usamos esta estructura para poder tener un
 *  teclado bufferizado, el bueffer es circular
 *  de tamanio BUFFER_SIZE. Es similar a una cola
 *  circular
 */

extern TTY tty[TTY_CANT];
extern int CurrentTTY;
extern PROCESS procesos[];
extern char *backupTerm;

tipoFlags flags[TTY_CANT];

// Seteo de que tipo va a ser el teclado
int layout;

/*
 *  Funciones static
 */

static int handler_scancode( unsigned char scancode );
static int is_shift_on( void );
static void set_modifier_key( int ascii, int set );
static int GraphicAscii( int ascii );
static int keypad( int ascii );
static int handler_scancode(unsigned char scancode);
static int BufferIsFull( void );
static int BufferIsEmpty( void );
static int get_word_stressed( int ascii );
static int arrows( int ascii );


void
Init_Flags( void )
{
    int i;
    for(i = 0; i< TTY_CANT; i++)
    {
        flags[i].caps_lock = 0;
        flags[i].num_lock = 0;
        flags[i].scroll_lock = 0;
        flags[i].rShift = 0;
        flags[i].lShift = 0;
        flags[i].rAlt = 0;
        flags[i].lAlt = 0;
        flags[i].rCtrl = 0;
        flags[i].lCtrl = 0;
        flags[i].accent = 0;
        flags[i].dieresis = 0;
        flags[i].enhanced = 0;
    }
    return;
}

/*
 *  keyboard_handler
 *  Maneja las interrupciones del teclado, ya sea
 *  pq se apreto una tecla o pq se solto detrminada tecla
 */

void
keyboard_handler( void )
{
    unsigned char scancode;
    int rta;
    int nextTTY;
    int indexTTY = CurrentTTY;


    /* Lee el scancode */
    ioIn(&scancode, (unsigned short)KEYBPORT);

    /* Apaga el flag de extendido antes
        * de llamar a la funcion que analiza
        * el scancode
        */

    flags[indexTTY].enhanced  = 0;

    /* analiza el scancode */

    rta = handler_scancode( scancode );

//     TODO: Sacar la negrada
    if ( scancode >= 0x3B && scancode <= 0x42 && flags[indexTTY].lAlt)
    {
        nextTTY= scancode - 0x3B;
        memcpy(backupTerm, (char*)0xb8000, VIDEO_WIDTH * VIDEO_HEIGTH * 2);
        memcpy((char*)0xb8000, tty[nextTTY].screen.address,
                            VIDEO_WIDTH * VIDEO_HEIGTH * 2);
        tty[CurrentTTY].screen.address = backupTerm;
        backupTerm = tty[nextTTY].screen.address;
        tty[nextTTY].screen.address = (char*)0xb8000;

//         copio flags
        flags[nextTTY].caps_lock = flags[CurrentTTY].caps_lock;
        flags[nextTTY].num_lock = flags[CurrentTTY].num_lock;
        flags[nextTTY].scroll_lock = flags[CurrentTTY].scroll_lock;
        flags[nextTTY].rShift = flags[CurrentTTY].rShift;
        flags[nextTTY].lShift = flags[CurrentTTY].lShift;
        flags[nextTTY].rAlt = flags[CurrentTTY].rAlt;
        flags[nextTTY].lAlt = flags[CurrentTTY].lAlt;
        flags[nextTTY].rCtrl = flags[CurrentTTY].rCtrl;
        flags[nextTTY].lCtrl = flags[CurrentTTY].lCtrl;
        flags[nextTTY].accent = flags[CurrentTTY].accent;
        flags[nextTTY].dieresis = flags[CurrentTTY].dieresis;
        flags[nextTTY].enhanced = flags[CurrentTTY].enhanced;

        CurrentTTY = nextTTY;
        move_csr(1);

        return;
    }

    if( rta == -1 )
            return;

    else if( rta == ACENTO  && layout == SPANISH_KB )
    {
        /* si anteriormente se apreto la tecla de tilde,
        se encola ' al buffer de teclado */
        if (flags[indexTTY].accent == 1)
        {
            enqueueBuffer(rta);
            flags[indexTTY].accent = 0;
        }
        else
            /* si no, activa flag de tilde presionada */
            flags[indexTTY].accent = 1;
    }
    else if (rta == DIERESIS && layout == SPANISH_KB)
    {
        if (flags[indexTTY].dieresis == 1)
        {
            enqueueBuffer(94);
            flags[indexTTY].dieresis = 0;
        }
        else
            flags[indexTTY].dieresis = 1;
    }
    else
    {
        /* Si la tecla anterior fue tilde (flag tilde se activo)
        y es vocal, obtiene el ascii de vocal tildada */
        if (( flags[indexTTY].accent || flags[indexTTY].dieresis ) && isvocal(rta))
        {
            rta = get_word_stressed(rta);
            enqueueBuffer( rta );
        }
        else
        {
            if (!( flags[indexTTY].accent || flags[indexTTY].dieresis ) )
                enqueueBuffer( rta );
        }
        flags[indexTTY].accent = 0;
        flags[indexTTY].dieresis = 0;
    }
    int i;
    for (i=0;i<MAX_PROC;i++)
    {
        if(procesos[i].blocked == 1 && procesos[i].tty==CurrentTTY)
        {
            procesos[i].blocked = 0;

        }
    }
    return ;

}

int
enqueueBuffer( int c )
{
    int indexTTY = CurrentTTY;


    if( !BufferIsFull() )
    {
        tty[indexTTY].buffer.buffer[tty[indexTTY].buffer.last++] = c;
        tty[indexTTY].buffer.cant++;

        if( tty[indexTTY].buffer.last ==  BUFFER_SIZE  )
            tty[indexTTY].buffer.last = 0;

        return OK;
    }

    return 0;
}

int
dequeueBuffer( int *c )
{
    int indexTTY = CurrentTTY;


    if( !BufferIsEmpty() )
    {
        *c = tty[indexTTY].buffer.buffer[tty[indexTTY].buffer.first++];
        tty[indexTTY].buffer.cant--;

        if( tty[indexTTY].buffer.first == BUFFER_SIZE )
            tty[indexTTY].buffer.first = 0;

        return OK;
    }

    return 0;
}

int
setByte( int c )
{
    return 1;
}

int
setLights(void * args)
{
    unsigned char * leds;
    unsigned char ack;
    unsigned short keybport;

    /* Bit0 -> Scroll lock
    * Bit1 -> Num lock
    * Bit2 -> Caps lock
    */

    leds = args;
    keybport = (unsigned short)KEYBPORT;


    ioOut(keybport, (unsigned char)SET_RESET);

    /* Espera el ACK del 8042 */
    do
    {
        ioIn(&ack, keybport);
    }
    while ( ack != ACK );

    /* Setea las nuevas luces */
    ioOut(keybport, *leds);

    /* Espera el ACK del 8042 */
    do
    {
        ioIn(&ack, keybport);
    }
    while ( ack != ACK );



    return 1;
}

/*
 *  Funciones estaticas
 */

void
set_key_map( int kbType )
{
    layout = kbType;
}



static int
handler_scancode(unsigned char scancode)
{
    int breakCode = 0;
    int ascii, shift;
    int set = 1;
    unsigned char leds;
    int indexTTY = CurrentTTY;



    if ( scancode == ENHANCED )
    {
        flags[indexTTY].enhanced = 1;
        ioIn(&scancode, (unsigned short)KEYBPORT);
    }

    shift = is_shift_on();

    if (scancode & CODETYPE)
        breakCode = 1;

    /* Como el teclado esta mapeado en make codes hay que
    * indexar el scancode como tal
    */

    ascii = key_layouts[layout][shift][scancode & 0x7f];

    if( ascii <= L_CTRL && ascii >= R_ALT )
    {
        if( scancode & CODETYPE )
            set = 0;

        set_modifier_key( ascii, set );
        return -1;
    }

    if( !breakCode )
    {
        if( flags[indexTTY].enhanced == 1 && ( ascii <= HOME && ascii >= DELETE ) )
            return arrows( ascii );

        if( !flags[indexTTY].enhanced && ( flags[indexTTY].num_lock==1 ) && ( ascii <= HOME && ascii >= DELETE ) )
            return keypad( ascii );

        if( isalnum( ascii ) || ascii >= 0 )
            return GraphicAscii( ascii );

        if( ascii == DIERESIS )
            return DIERESIS;

        if( ascii == CPS_LCK  )
        {
            flags[indexTTY].caps_lock = !flags[indexTTY].caps_lock ;
            leds = (unsigned char)LEDS(flags[indexTTY].caps_lock, flags[indexTTY].num_lock, flags[indexTTY].scroll_lock);
            setLights(&leds);

            return -1;
        }

        if( ascii == NUM_LCK  )
        {
            flags[indexTTY].num_lock = !flags[indexTTY].num_lock;
            leds = (unsigned char)LEDS(flags[indexTTY].caps_lock, flags[indexTTY].num_lock, flags[indexTTY].scroll_lock);
            setLights(&leds);

            return -1;
        }

        if( ascii == SCR_LCK )
        {
            flags[indexTTY].scroll_lock = !flags[indexTTY].scroll_lock;
            leds = (unsigned char)LEDS(flags[indexTTY].caps_lock, flags[indexTTY].num_lock, flags[indexTTY].scroll_lock);
            setLights(&leds);

            return -1;
        }
    }

    return -1;
}


static int
is_shift_on( void )
{
    int indexTTY = CurrentTTY;


    return ( flags[indexTTY].rShift || flags[indexTTY].lShift );
}

static int
get_word_stressed( int ascii )
{
    int indexTTY = CurrentTTY;

    int rta;
    if (flags[indexTTY].accent)
    {
        switch( ascii )
        {
            case 'a':
                rta = 160;
                break;

            case 'e':
                rta = 130;
                break;

            case 'i':
                rta = 161;
                break;

            case 'o':
                rta = 162;
                break;

            case 'u':
                rta = 163;
                break;

            case 'E':
                rta = 144;
                break;

            default:
                rta = ascii;
                break;
        }
    }
    else if (flags[indexTTY].dieresis)
    {
        switch( ascii )
        {
            case 'a':
                rta = 132;
                break;

            case 'e':
                rta = 137;
                break;

            case 'i':
                rta = 139;
                break;

            case 'o':
                rta = 148;
                break;

            case 'u':
                rta = 129;
                break;

            case 'A':
                rta = 142;
                break;
            case 'O':
                rta = 153;
                break;
            case 'U':
                rta = 154;
                break;
            default:
                rta = ascii;
                break;
        }
    }

    return rta;
}



static void
set_modifier_key( int ascii, int set )
{
    int indexTTY = CurrentTTY;


    switch( ascii )
    {
        case R_CTRL:
            flags[indexTTY].rCtrl = set;
            break;

        case L_CTRL:
            flags[indexTTY].enhanced ? ( flags[indexTTY].rCtrl = set ) : ( flags[indexTTY].lCtrl = set );
            break;

        case R_ALT:
            flags[indexTTY].rAlt = set;
            break;

        case L_ALT:
            flags[indexTTY].enhanced ? ( flags[indexTTY].rAlt = set ) : ( flags[indexTTY].lAlt = set );
            break;

        case L_SHIFT:
            flags[indexTTY].lShift = set;
            break;

        case R_SHIFT:
            flags[indexTTY].rShift = set;
            break;
    }
}

static int
arrows( int ascii )
{
    int rta;

    switch( ascii )
    {
        case UP:
            rta = ascii;
            break;

        case DOWN:
            rta =  ascii;
            break;

        case RIGHT:
            rta =  ascii;
            break;

        case LEFT:
            rta = ascii;
            break;

        default:
            rta = -1;
    }

    return rta;
}

static int
GraphicAscii( int ascii )
{
    int indexTTY = CurrentTTY;


    if( flags[indexTTY].caps_lock == 1 )
    {
        if( !is_shift_on() )
            return toupper( ascii );
        else
            return tolower( ascii );
    }
    else
        return ascii;
}

static int
keypad( int ascii )
{
    return ( num_keyboard[ ( ascii * -1 ) + HOME ] );
}

static int
BufferIsFull( void )
{
    int indexTTY = CurrentTTY;


    return ( tty[indexTTY].buffer.cant == ( BUFFER_SIZE -1 ) );
}

static int
BufferIsEmpty( void )
{
    int indexTTY = CurrentTTY;

    return ( tty[indexTTY].buffer.cant == 0 );
}




